; ******************************************************************************
; FILE:    MOD player of the MD's Z80
; DESCRIPTION:
;    Driver to play MOD like music generated by MVS Tracker
;
; HISTORY:
;    V0.1 09May2006 by Pascal
;        Initial version
;    v0.2 22Aug2006 by Kaneda
;        MVSTracker and modplas has now 11 frequency (and not 12 for modplay)
;    v0.3 12Apr2007 by Kaneda
;        DAC+PSG support finished
;
;   Modified for SJasm by Stef
;
;******************************************************************************

; ************************
; TODO:
; - use PSGChanel to enable/disable
; - better use of PSG chan (3 files playing same time => 3 bank switch) ?
; - PSG/FM flag in VInt to bankswitch only on loop and avoid bankswitch corruption
; - GetXXXadress as function
; - look at Stef optimized driver to compare/update
; ************************


STACK           EQU $2000
RAM_ADDR        EQU $1500


CMD_PLAY_ONCE   EQU $1
CMD_PLAY_LOOP   EQU $2
CMD_SILENCE     EQU $0

FX_NOTHING      EQU $00
FX_PLAY         EQU $01
FX_UPDATE       EQU $80
FX_STOP         EQU $ff

PSG_1CH         EQU $1
PSG_2CH         EQU $2
PSG_3CH         EQU $3
PSG_SILENT      EQU $0


; ******************************************************************************
; Macros
; ******************************************************************************


; ******************
    macro sendDEPortA

.Wait4000
    ld a, ($4000)
    and 8
    jr nz, .Wait4000
    ld    a, d
    ld    ($4000),a

.Wait4001
    ld a, ($4001)
    and 8
    jr nz, .Wait4001

    ld    a, e
    ld    ($4001),a
    endm

; ******************
    macro sendDEPortB

.Wait4002
    ld a, ($4002)
    and 8
    jr nz, .Wait4002
    ld    a, d
    ld    ($4002),a

.Wait4003
    ld a, ($4003)
    and 8
    jr nz, .Wait4003
    ld    a, e
    ld    ($4003),a
    endm

; ******************************************************************************
; Instrument# 0..31 multiply by 32
; result in de
; ******************************************************************************
    macro    loadDEinstrX32
    push hl
    ld hl, 0
    sla a    ; x2
    sla a    ; x4
    sla a    ; x8
    ld l, a
    add hl, hl ; x16
    add hl, hl ; x32
    ex de, hl
    pop hl
    endm

; ******************************************************************************
; Update ticks and call updatetune if needed
; ******************************************************************************
    macro UpdateTicks
    ld a, (TempoCount)
    inc a
    ld (TempoCount), a
    ld d, a
    ld a, (Tempo)
    cp d
    jr nz, .IRQ_1

    xor a
    ld (TempoCount), a
    call UpdateTune

.IRQ_1
    ld de, 273fh        ; restart timers
    sendDEPortA
    endm


; ******************
; Set up the bank of the Z80
; TODO : make a func and use Stef way, optimized
;
    macro GetTuneAddress
    ld hl,$6000
    ld a,(MidAddrTune)
    rlca
    ld (hl),a
    ld a,(HighAddrTune)
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a

    ld a,(LowAddrTune)
    ld l,a
    ld a,(MidAddrTune)
    or $80
    ld h,a ; HL= address of the tune in Z80 address space
    endm

    macro GetFXAddress
    ld hl,$6000
    ld a,(MidAddrFx)
    rlca
    ld (hl),a
    ld a,(HighAddrFx)
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a

    ld a,(LowAddrFx)
    ld l,a
    ld a,(MidAddrFx)
    or $80
    ld h,a ; HL= address of the tune in Z80 address space
    endm

    macro IncrementPSG
;;; increment PSG adress
    ld hl,LowAddrPSG
    ld a,(hl)
    add a,1
    ld (hl),a
    jp    nc,.IncOK


    inc hl
    ld a,(hl)
    add a, 1
    ld (hl),a
    jp    nc,.IncOK

    inc hl
    ld  a,(hl)
    add a,1
    ld  (hl),a
.IncOK

    endm

; ******************************************************************************
; Variables
; ******************************************************************************

base            EQU $1500

; command   1: play once
;           2: loop play
;           0: silence
Command         EQU base+$0
PreviousCmd     EQU base+$1

; special FM
TuneData        EQU base+$2
SongPos         EQU base+$4
SongLen         EQU base+$5
FreqHi          EQU base+$6
FreqLo          EQU base+$7
Block           EQU base+$8
Instr           EQU base+$9
Effect          EQU base+$a
Value           EQU base+$b
CurInstr        EQU base+$c
PatternPos      EQU base+$10
PatternCnt      EQU base+$12
TempoCount      EQU base+$13
Tempo           EQU base+$14
InstrStart      EQU base+$15
TuneStart       EQU base+$17
PatternBreak    EQU base+$19
InstantPlay     EQU base+$1A

; Flags
MusicPlaying    EQU base+$1B
FxPlaying       EQU base+$1C

; custom for megadrive banking system
LowAddrTune     EQU base+$1E
MidAddrTune     EQU base+$1F
HighAddrTune    EQU base+$20
FxCommand       EQU base+$21
FxData          EQU base+$22
LowAddrFx       EQU base+$24
MidAddrFx       EQU base+$25
HighAddrFx      EQU base+$26
SizeFx          EQU base+$27
PSGCommand      EQU base+$29
PSGChannel      EQU base+$2A
LowAddrPSG      EQU base+$2B
MidAddrPSG      EQU base+$2C
HighAddrPSG     EQU base+$2D
PSGPlaying      EQU base+$2E



; ******************************************************************************
; Code
; ******************************************************************************

    ORG $0000

    di
    jp EntryPoint


; ******************************************************************************
; VBlank interrupts - used for keeping the music going
; ******************************************************************************
    BLOCK   $0038-$

    di
    push af
    push de


;;;;;;;;;;;;; PSG
    ld a,(PSGCommand)
    cp 0
    jp z, psgPlay

    cp 0ffh
    jp z, psgStop

psgInit
    xor a
    ld (PSGCommand), a

    call GetPSGAddress

    ld a, (hl)
    ld (PSGPlaying), a

    IncrementPSG

psgPlay
    ld a, (PSGPlaying)
    cp PSG_SILENT
    jp z, psgEnd

    call GetPSGAddress

    ld a, (hl)
    cp 0D0h
    jp nz, psgOK

psgStop
    xor a
    ld (PSGPlaying), a

    ld a, $80 ; no data
    ld ($7f11),a
    ld a, $9F ; no volume
    ld ($7f11),a

    ld a, $80 ; no data
    ld ($7f11),a
    ld a, $9F ; no volume
    ld ($7f11),a

    ld a, $A0 ; no data
    ld ($7f11),a
    ld a, $BF ; no volume
    ld ($7f11),a

    ld a, $C0 ; no data
    ld ($7f11),a
    ld a, $DF ; no volume
    ld ($7f11),a

    jp psgEnd

psgOK
    ; chan1
    ld c, 0 ; $00
    call UpdatePSG
    ld a, (PSGPlaying)
    cp PSG_1CH
    jr z,psgEnd

    ; chan2
    ld c, 1 ;$20
    call UpdatePSG
    ld a, (PSGPlaying)
    cp PSG_2CH
    jr z,psgEnd

    ; chan 3
    ld c, 2 ;$40
    call UpdatePSG

psgEnd

;;;;;;;;;;;;;;;;;;; FM
    call ProcessCommand
    UpdateTicks

    pop de
    pop af
    ei
    reti

; ******************************************************************************
; Entry point
; ******************************************************************************
EntryPoint
    ld sp,STACK
    im 1h

    ; do any initialisation
    ld de, 2730h
    sendDEPortA
    ld de, 273fh    ; start timers
    sendDEPortA

    ld de,9000h ; special YM MD register
    sendDEPortA

    ld a, 6 ; default tempo
    ld (Tempo), a

    ld a, 7 ; 3chanels
    ld (PSGChannel), a

    xor a
    ld (MusicPlaying), a
    ld (PreviousCmd),a
    ld (FxPlaying),a
    ei


MainLoop

;;;;;;;; DAC
; check if the cmd changed
    ld a,(FxCommand)

; check the id of the command
    cp FX_UPDATE
    jr z,FxUpdate

    cp FX_PLAY
    jr z,FxPlay

    cp FX_STOP
    jr z,FxStop

    jp FxEnd

FxStop
    ld de, 2B00h ; disable DAC
    sendDEPortA

    xor a
    ld (FxCommand),a
    ld (FxPlaying),a
    jp FxEnd

FxPlay
    call InitFx

    ld a,FX_UPDATE
    ld (FxCommand),a
    ld a, 1
    ld (FxPlaying), a
    jp FxEnd

FxUpdate
    ld a, (FxPlaying)
    and a ; faster than     cp 0
    jr z, FxEnd

    GetFXAddress


;;;;;;;;;;;;;;;;;;;
;; too slow!!!    ;;;
;    ld a, (hl)
;    ld d, $2A
;    ld e, a
;    sendDEPortA

  ld a,$2a           ; store byte to DAC
  ld ($4000),a
  ld a,(hl)
  ld ($4001),a

;;;;;;;;;;;;;;;;;;;;;;; increment sound address
;;;;;; method 1: 8317Hz
;    ld hl,LowAddrFx
;    ld a,(hl)
;    add a,1
;    ld (hl),a
;    inc hl
;    ld a,(hl)
;    adc a, 0
;    ld (hl),a
;    inc hl
;    ld  a,(hl)
;    adc a,0
;    ld  (hl),a

;;;;;;; method 2: 8817Hz
;    ld    bc, (LowAddrFx)
;    inc bc
;    ld  a,b
;    or  c
;    ld     (LowAddrFx),bc
;    jp  nz, FxUpdate2
;
;    ld    bc, (HighAddrFx)
;    inc bc
;    ld     (HighAddrFx),bc
;FxUpdate2:

;;;;;;; method 3: 8817Hz
;    ld    bc, (LowAddrFx)
;    inc bc
;    ld  a,b
;    or  c
;    ld     (LowAddrFx),bc
;    jp  nz, FxUpdate2
;
;    ld hl,HighAddrFx
;    ld  a,(hl)
;    add a,1
;    ld  (hl),a
;FxUpdate2:

;;;;;; method 4: 9346Hz
    ld hl,LowAddrFx    ;16
    ld a,(hl);7
    add a,1 ;4
    ld (hl),a;7
    jp    nc,FxUpdate2 ;10

    inc hl
    ld a,(hl)
    add a, 1
    ld (hl),a
    jp    nc,FxUpdate2

    inc hl
    ld  a,(hl)
    add a,1
    ld  (hl),a
FxUpdate2:


;;;;;;;;;;;;;;;;;;;;;;;;;;
    ld     bc, (SizeFx)
    dec bc ; does not set Z flag even BC is 0 :(
    ld  a,b
    or  c
    jp  z, FxStop

    ld     (SizeFx),bc

FxEnd

    jp MainLoop


;;;;
;;; voice and chan loading aren't in commun_func because md and neo don't use the same chanels (0,1,4,5 vs 1,2,5,6)
;;; MD could use neo one but since 2&6 are special chanels, I think you should avoid this config
;;;
; *******************************************************************************
; Voice Loading
; call LoadVoice1..4 to load the instrument data for that voice
; Inputs: IY pointer to instrument data
; *******************************************************************************
LoadVoice1 ; PortA
    ld de, 2800h    ; turn off voice 1
    sendDEPortA

    ld ix, InstrAddr0
    ld b, 25
LV1_1
    ld a, (ix+0)
    ld d, a
    ld a, (iy+0)
    ld e, a
    sendDEPortA        ; store data on Port A
    inc ix
    inc iy
    djnz LV1_1
    ret

LoadVoice2 ; PortA
    ld de, 2801h    ; turn off voice 2
    sendDEPortA

    ld ix, InstrAddr1
    ld b, 25
LV2_1
    ld a, (ix+0)
    ld d, a
    ld a, (iy+0)
    ld e, a
    sendDEPortA        ; store data on Port A
    inc ix
    inc iy
    djnz LV2_1
    ret

LoadVoice3 ; PortB
    ld de, 2804h    ; turn off voice 3
    sendDEPortA

    ld ix, InstrAddr0
    ld b, 25
LV3_1
    ld a, (ix+0)
    ld d, a
    ld a, (iy+0)
    ld e, a
    sendDEPortB        ; store data on Port B
    inc ix
    inc iy
    djnz LV3_1
    ret

LoadVoice4 ; PortB
    ld de, 2805h    ; turn off voice 4
    sendDEPortA

    ld ix, InstrAddr1
    ld b, 25
LV4_1
    ld a, (ix+0)
    ld d, a
    ld a, (iy+0)
    ld e, a
    sendDEPortB        ; store data on Port B
    inc ix
    inc iy
    djnz LV4_1
    ret

; *******************************************************************************
; FUNCTION:    StopSong
; DESCRIPTION:    stop the 4 chan
; PARAMETERS:
; OUTPUTS:
; *******************************************************************************
StopSong
        ; silence the channels and stop the music playing
    ld de, 2800h    ; key up on voice 1
    sendDEPortA
    ld de, 2801h    ; key up on voice 2
    sendDEPortA
    ld de, 2804h    ; key up on voice 4
    sendDEPortA
    ld de, 2805h    ; key up on voice 5
    sendDEPortA
    ret

; *******************************************************************************
; FUNCTION:    UpdateChan1
; DESCRIPTION:    update 1st chanel
; PARAMETERS:
; OUTPUTS:
; *******************************************************************************
UpdateChan1
    call ReadChannelInfo
    ; if the Freq is 0 then nothing to do
    ld a, (FreqHi)
    or a
    jr nz, UT_Process1
    ld a, (FreqLo)
    or a
    jr nz, UT_Process1
    ld a, 1
    call ProcessEffect
    ret

UT_Process1
    ; do we have to load a new instrument?
    ld a, (Instr)
    ld hl, CurInstr
    cp (hl)
    jr z, UT_Process1_1

    ; load a new instrument
    call GetInstrOffset
    call LoadVoice1
    ld a, (Instr)
    ld (CurInstr), a
UT_Process1_1
    ld a, 1
    call ProcessEffect
    ret c

    ; play voice 0
    ld de, 2800h
    sendDEPortA
    ld de, 28f0h
    sendDEPortA

    ld a, (FreqHi)
    ld b, a
    ld a, (Block)
    or b
    ld d, 0a4h
    ld e, a
    sendDEPortA
    ld a, (FreqLo)
    ld d, 0a0h
    ld e, a
    sendDEPortA
    ret


; *******************************************************************************
; FUNCTION:    UpdateChan2
; DESCRIPTION:    update 2nd chanel
; PARAMETERS:
; OUTPUTS:
; *******************************************************************************
UpdateChan2
    call ReadChannelInfo
    ; if the freq is 0 then nothing to do
    ld a, (FreqHi)
    or a
    jr nz, UT_Process2
    ld a, (FreqLo)
    or a
    jr nz, UT_Process2
    ld a, 2
    call ProcessEffect
    ret

UT_Process2
    ; do we have to load a new instrument?
    ld a, (Instr)
    ld hl, CurInstr+1
    cp (hl)
    jr z, UT_Process2_1

    ; load a new instrument
    call GetInstrOffset
    call LoadVoice2
    ld a, (Instr)
    ld (CurInstr+1), a
UT_Process2_1
    ld a, 2
    call ProcessEffect
    ret c

    ; play voice 1
    ld de, 2801h
    sendDEPortA
    ld de, 28f1h
    sendDEPortA
    ld a, (FreqHi)
    ld b, a
    ld a, (Block)
    or b
    ld d, 0a5h
    ld e, a
    sendDEPortA
    ld a, (FreqLo)
    ld d, 0a1h
    ld e, a
    sendDEPortA
    ret


; *******************************************************************************
; FUNCTION:    UpdateChan3
; DESCRIPTION:    update 3rd chanel
; PARAMETERS:
; OUTPUTS:
; *******************************************************************************
UpdateChan3
    call ReadChannelInfo
    ; if the freq is 0 then nothing to do
    ld a, (FreqHi)
    or a
    jr nz, UT_Process3
    ld a, (FreqLo)
    or a
    jr nz, UT_Process3
    ld a, 3
    call ProcessEffect
    ret

UT_Process3
    ; do we have to load a new instrument?
    ld a, (Instr)
    ld hl, CurInstr+2
    cp (hl)
    jr z, UT_Process3_1

    ; load a new instrument
    call GetInstrOffset
    call LoadVoice3
    ld a, (Instr)
    ld (CurInstr+2), a
UT_Process3_1
    ld a, 3
    call ProcessEffect
    ret c

    ; play voice 3
    ld de, 2804h
    sendDEPortA
    ld de, 28f4h
    sendDEPortA
    ld a, (FreqHi)
    ld b, a
    ld a, (Block)
    or b
    ld d, 0a4h
    ld e, a
    sendDEPortB
    ld a, (FreqLo)
    ld d, 0a0h
    ld e, a
    sendDEPortB
    ret


; *******************************************************************************
; FUNCTION:    UpdateChan4
; DESCRIPTION:    update 4th chanel
; PARAMETERS:
; OUTPUTS:
; *******************************************************************************
UpdateChan4
    call ReadChannelInfo
    ; if the freq is 0 then nothing to do
    ld a, (FreqHi)
    or a
    jr nz, UT_Process4
    ld a, (FreqLo)
    or a
    jr nz, UT_Process4
    ld a, 4
    call ProcessEffect
    ret

UT_Process4
    ; do we have to load a new instrument?
    ld a, (Instr)
    ld hl, CurInstr+3
    cp (hl)
    jr z, UT_Process4_1

    ; load a new instrument
    call GetInstrOffset
    call LoadVoice4
    ld a, (Instr)
    ld (CurInstr+3), a
UT_Process4_1
    ld a, 4
    call ProcessEffect
    ret c

    ; play voice 4
    ld de, 2805h
    sendDEPortA
    ld de, 28f5h
    sendDEPortA
    ld a, (FreqHi)
    ld b, a
    ld a, (Block)
    or b
    ld d, 0a5h
    ld e, a
    sendDEPortB
    ld a, (FreqLo)
    ld d, 0a1h
    ld e, a
    sendDEPortB
    ret


; *******************************************************************************
; FUNCTION:    InitFx
; DESCRIPTION:    Enable dac and fx addr in z80 space
; PARAMETERS:    none
; OUTPUTS:
; *******************************************************************************
InitFx:
    ; convert 68k address to z80 address
    GetFXAddress

    ; save it to Fx vars
    ld a, l
    ld (FxData), a
    ld a, h
    ld (FxData+1), a

     ; enable DAC
    ld de, 2B80h
    sendDEPortA

    ret


; *******************************************************************************
; FUNCTION:    UpdatePSG
; DESCRIPTION:    update the PSG with AFX data
; PARAMETERS:    c = chan mask, hl = current AFX data adr
; OUTPUTS: hl = new AFX data adr
; *******************************************************************************
UpdatePSG
    ld a, (hl)
    ld b, a ;sauvegarde

    rrc c
    rrc c
    rrc c

    and 0fh
    sub 0fh
    jp     p, volpos
    neg

volpos
    or     90h ;latch/volume
    or c
    ld (7f11h),a

    ;handle data
    ld a, b
    and 20h
    jp z, noTone

    ; data 1 : LSB
    IncrementPSG
    call GetPSGAddress
    ld a, (hl)
    and 0fh
    or 80h ;latch/tone
    or c
    ld (7f11h),a

    ld a, (hl)
    srl a
    srl a
    srl a
    srl a
    ld c, a

    ; data 2
    IncrementPSG
    call GetPSGAddress
    ld a, (hl)
    and 3h
    sla a
    sla a
    sla a
    sla a
    or c
    ld (7f11h),a

noTone
    ld a, b
    and 40h
    jp z, noNoise


    ;; handle noise
    IncrementPSG
    call GetPSGAddress
    ;; TODO

noNoise
    IncrementPSG
    call GetPSGAddress
    ret

UpdatePSG2
    ld a, (hl)
    ld b, a ;sauvegarde

    and 0fh
    sub 0fh
    jp     p, volpos2
    neg

volpos2
    or     0B0h ;latch/volume
    ld (7f11h),a

    ;handle data
    ld a, b
    and 20h
    jp z, noTone2

    ; data 1 : LSB
    IncrementPSG
    call GetPSGAddress
    ld a, (hl)
    and 0fh
    or 0A0h ;latch/tone
    ld (7f11h),a

    ld a, (hl)
    srl a
    srl a
    srl a
    srl a
    ld c, a

    ; data 2
    IncrementPSG
    call GetPSGAddress
    ld a, (hl)
    and 3h
    sla a
    sla a
    sla a
    sla a
    or c
    ld (7f11h),a

noTone2
    ld a, b
    and 40h
    jp z, noNoise2


    ;; handle noise
    IncrementPSG
    call GetPSGAddress
    ;; TODO

noNoise2
    IncrementPSG
    call GetPSGAddress
    ret

; *******************************************************************************
; FUNCTION:    GetPSGAddress
; DESCRIPTION:    init Z80 bank and get z80 address in HL
; PARAMETERS:    -
; OUTPUTS: hl = new address
; *******************************************************************************
GetPSGAddress
    ld hl,$6000
    ld a,(MidAddrPSG)
    rlca
    ld (hl),a
    ld a,(HighAddrPSG)
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a
    rrca
    ld (hl),a

    ld a,(LowAddrPSG)
    ld l,a
    ld a,(MidAddrPSG)
    or $80
    ld h,a ; HL= address of the tune in Z80 address space
    ret


; *******************************************************************************
; Process command
; use AF & DE
; *******************************************************************************
ProcessCommand
    push af
    push de

; check if the cmd changed
    ld a,(Command)
    ld d,a
    ld a,(PreviousCmd)
    sub d
    jp z,NoChange

; check the id of the command
    ld a,(Command)
    cp CMD_PLAY_ONCE
    jr z,Play

    cp CMD_PLAY_LOOP
    jr z,Play

    jr Silence

Play
    ld b, 1
    call InitSong
    jr ProcessCommand_End

Silence
    call StopSong

    xor a
    ld (MusicPlaying), a

ProcessCommand_End
    ld a,(Command)
    ld (PreviousCmd),a

NoChange
    pop de
    pop af
    ret

; *******************************************************************************
; FUNCTION:    InitSong
; DESCRIPTION:    Clears variables and sets up the first pattern
; PARAMETERS:    b = TuneNumber, 1 based.
; OUTPUTS:
; *******************************************************************************
InitSong
    ; find start of song
    xor a
    ld (MusicPlaying), a

    ld de, 0
    GetTuneAddress

    ld a, l    ; set the tune data vars
    ld (TuneData), a
    ld a, h
    ld (TuneData+1), a

IS_NextTune
    add hl, de
    call CheckHeader
    ret c ; if tune doesn't exist then exit

    ; get offset to next tune
    ld a, (hl)
    ld e, a
    inc hl
    ld a, (hl)
    ld d, a
    inc hl

    ; keep getting next tune until b = 0
    dec b
    ld a, b
    or a
    jr nz, IS_NextTune

    ld a, (hl) ; number of instruments
    ld b, a ; tmp store
    ; store instrument start position
    inc hl
    ld a, l
    ld (InstrStart), a
    ld a, h
    ld (InstrStart+1), a

    ; find start of tune data
    ld a, b
    loadDEinstrX32
    add hl, de
    ld a, l
    ld (TuneStart), a
    ld a, h
    ld (TuneStart+1), a

    ; initialise
    ld a, (hl)
    ld (SongLen), a

    xor a
    ld (SongPos), a
    ld (TempoCount), a
    ld (PatternBreak), a

    ld a, 0ffh
    ld (CurInstr+0), a
    ld (CurInstr+1), a
    ld (CurInstr+2), a
    ld (CurInstr+3), a

    call InitialiseNewPattern
    ld a, 1
    ld (MusicPlaying), a
    ret

; *******************************************************************************
; FUNCTION:    CheckHeader
; DESCRIPTION:    Checks to ensure the bytes MVS0 are present to
; PARAMETERS:    hl - pointer to header
; OUTPUTS:    cflag - set if check fails
;        hl - updated to point to byte after header
; *******************************************************************************
CheckHeader
    ld a,(hl)
    cp 77    ; 'M'
    jr nz, CheckHeader_Fail
    inc hl
    ld a, (hl)
    cp 86    ; 'V'
    jr nz, CheckHeader_Fail
    inc hl
    ld a, (hl)
    cp 83 ; 'S'
    jr nz, CheckHeader_Fail
    inc hl
    ld a, (hl)
    cp 48 ; '0'
    jr nz, CheckHeader_Fail
    inc hl
    ; success
    ;;scf
    ;;ccf
    or a ; faster
    ret

CheckHeader_Fail
    scf
    ret

; *******************************************************************************
; FUNCTION:    InitialiseNewPattern
; DESCRIPTION:    Sets up the PatternPos pointer and PatternCnt
; PARAMETERS:    variables given the Song position
; *******************************************************************************
InitialiseNewPattern
    ; get the pattern number in 'a'
    ld a, (SongPos)
    ld e, a
    ld d, 0
    call loadHLTune
    inc hl ; go to Pattern Table
    add hl, de
    ld a, (hl)
    ; multiple by 1024 to get a pattern offset
    sla a
    sla a
    ld d, a
    ld e, 0
    ; add pointer to first pattern
    call loadHLTune
    push de
    ld de, 129 ; skip pattern info
    add hl, de
    pop de
    add hl, de
    ; store pattern pos
    ld a, h
    ld (PatternPos), a
    ld a, l
    ld (PatternPos+1), a
    xor a
    ld (PatternCnt), a
    ret

; *******************************************************************************
; FUNCTION:    GetInstrOffset
; DESCRIPTION:    Given an instrument number (0..31) a pointer to
;        that instrument is returned
; PARAMETERS:    a = instrument # in range (0..31)
; Ouput        Output: iy = point to instrument data
; *******************************************************************************
GetInstrOffset
    push de
    push hl
    ; Instr# ; 32
    loadDEinstrX32
    ; Add instrument table base address
    call loadHLInstruments
    add hl, de
    ; transfer result to iy
    push hl
    pop iy
    ; tidy up
    pop hl
    pop de
    ret


; *******************************************************************************
; Helper fonction
; *******************************************************************************
; load hl with addr of TuneStart for current tune
loadHLTune
    ld a, (TuneStart)
    ld l, a
    ld a, (TuneStart+1)
    ld h, a
    ret

; load hl with addr of InstrStart for current tune
loadHLInstruments
    ld a, (InstrStart)
    ld l, a
    ld a, (InstrStart+1)
    ld h, a
    ret


; *******************************************************************************
; FUNCTION:    UpdateTune
; DESCRIPTION:
; PARAMETERS:
; *******************************************************************************
UpdateTune
    ld a, (MusicPlaying)
    or a
    ret z

    GetTuneAddress

    call UpdateChan1
    call UpdateChan2
    call UpdateChan3
    call UpdateChan4

;UT_UpdatePattern
    ; update index to check if we have finished this pattern
    ld a, (PatternCnt)
    inc a
    ld (PatternCnt), a
    cp 64
    jr z, UT_CheckSong
    ld a, (PatternBreak)
    or a
    jp z, UpdateTune_End
    xor a
    ld (PatternBreak), a
UT_CheckSong
    ; get next pattern
    ld a, (SongPos)
    inc a
    ld (SongPos), a
    ; check if there are any patterns left
    ld hl, SongLen
    cp (hl)
    jp nz, UT_NextPat

;; TODO Attention boucle sur la zique ici
;    ld a,(Command)
;    cp CMD_PLAY_LOOP
;    jp z,loopSong
;
;    xor a
;    ld (MusicPlaying),a
;    ld (Command),a
;
;    call ProcessCommand
;
;    jr UT_NextPat
;
;loopSong
    xor a
    ld (SongPos), a

UT_NextPat
    call InitialiseNewPattern

UpdateTune_End
    ret


; *******************************************************************************
; ReadChannelInfo
; Reads in a set of 4 bytes representing Frequency,
; Instrument, Effect and the Effect Value
; Once the bytes have been read the PatternPos pointer is updated
; *******************************************************************************
ReadChannelInfo
    ; load Pattern pointer into hl
    ld a, (PatternPos)
    ld h, a
    ld a, (PatternPos+1)
    ld l, a
    ; load block - into bits 3-5
    ld a, (hl)
    and 0f0h
    sra a
    ld (Block), a

    ; load FNum Index
    ld a, (hl)
    and 0fh
    sla a
    ld e, a
    ld d, 0
    ld ix, FNTable
    add ix, de
    ld a, (ix+0)
    ld (FreqHi), a
    ld a, (ix+1)
    ld (FreqLo), a
    inc hl
    ; load instrument (range 1-31, convert to 0-...)
    ld a, (hl)
    or a
    jr z, RCI_1
    dec a
RCI_1
    ld (Instr), a
    inc hl
    ; load effect
    ld a, (hl)
    ld (Effect), a
    inc hl
    ; load effect value
    ld a, (hl)
    ld (Value), a
    inc hl
    ; store updated values for PatternPos
    ld a, h
    ld (PatternPos), a
    ld a, l
    ld (PatternPos+1),a
    ret

; *******************************************************************************
; ReadChannelInfo data
; Frequency table C - B
; *******************************************************************************
FNTable
    .db 0,0, 4, 0d2h, 2, 8eh, 2, 0b5h, 2, 0deh, 3, 09h, 3, 38h
    .db 3, 69h, 3, 9ch, 3, 0d3h, 4, 0eh, 4, 4bh, 4, 8dh

; *******************************************************************************
; Voice Loading data
; *******************************************************************************
InstrAddr0 ; MD 1
    DB 30h,34h,38h,3ch,40h,44h,48h,4ch,50h,54h,58h,5ch
    DB 60h,64h,68h,6ch,70h,74h,78h,7ch,80h,84h,88h,8ch
    DB 0b0h

InstrAddr1 ; MD 2, Neo 1
    DB 31h,35h,39h,3dh,41h,45h,49h,4dh,51h,55h,59h,5dh
    DB 61h,65h,69h,6dh,71h,75h,79h,7dh,81h,85h,89h,8dh
    DB 0b1h

InstrAddr2 ; MD 3 (unused), Neo 2
    DB 32h,36h,3ah,3eh,42h,46h,4ah,4eh,52h,56h,5ah,5eh
    DB 62h,66h,6ah,6eh,72h,76h,7ah,7eh,82h,86h,8ah,8eh
    DB 0b2h

BlankInstr
    DB 0,0,0,0, 0,0,0,0, 0,0,0,0
    DB 0,0,0,0, 0,0,0,0, 0,0,0,0
    DB 0


; *******************************************************************************
; Processes the Effect Value
; Inputs: a = channel 1..4
; Outputs: cy = silence, dont play note
; *******************************************************************************
ProcessEffect
    ld e, a

    ; TODO: Effect and Value
    ld a, (Effect)
    cp 12 ; volume
    jr z, PE_ProcessVol
    cp 15 ; tempo
    jr z, PE_ProcessTempo
    cp 13 ; pattern break
    jr z, PE_ProcessPatternBreak
PE_End
    scf
    ccf
    ret

PE_ProcessVol
    ; if vol = 0 then cut channel
    ld a, (Value)
    or a
    jr nz, PE_End
    ld d, 0
    ld hl, PE_Chan
    add hl, de
    ld a, (hl)
    ld d, 28h
    ld e, a
    sendDEPortA
    scf
    ret

;; warning : no handle of PAL/NTSC so be careful
PE_ProcessTempo
    ld a, (Value)
    ld (Tempo), a
    scf
    ret

; flag the pattern to end
PE_ProcessPatternBreak
    ld a, 1
    ld (PatternBreak), a
    scf
    ret


; *******************************************************************************
; ProcessEffect data
; *******************************************************************************
PE_Chan
    DB 0,0,1,4,5
